#include "arithmetic.h"

#include "token.h"
#include "utils.h"

using Tag = Token::Tag;

ArithOp::ArithOp(int tag) {
    std::string pat = ",+-*/%!<>ENLG&|FI";
    Utils::Assert(pat.find(tag) != pat.npos, "invalid operation");
    op = tag;
}

bool ArithOp::IsUnary() const {
    return op == Add || op == Sub || op == Not || op == FloatCast ||
           op == IntCast;
}

bool ArithOp::IsBinary() const {
    return op != Not && op != FloatCast && op != IntCast;
}

ArithValue ArithOp::Calc(ArithValue lhs, ArithValue rhs, ArithOp op) {
    Utils::Assert(lhs.type == rhs.type, "operands should have the same type");
    if (lhs.type == Type::Basic::Int) {
        auto L = lhs.v.i, R = rhs.v.i;
        switch (op) {
            case Comma:
                return ArithValue(R);
            case Add:
                return ArithValue(L + R);
            case Sub:
                return ArithValue(L - R);
            case Mul:
                return ArithValue(L * R);
            case Div:
                return ArithValue(L / R);
            case Mod:
                return ArithValue(L % R);
            case Less:
                return ArithValue(L < R);
            case Greater:
                return ArithValue(L > R);
            case Equal:
                return ArithValue(L == R);
            case NotEqual:
                return ArithValue(L != R);
            case LessEqual:
                return ArithValue(L <= R);
            case GreaterEqual:
                return ArithValue(L >= R);
            case LogicalAnd:
                return ArithValue(L && R);
            case LogicalOr:
                return ArithValue(L || R);
            default:
                Utils::Err("not a valid integer binary operation");
        }
    } else if (lhs.type == Type::Basic::Float) {
        auto L = lhs.v.f, R = rhs.v.f;
        switch (op) {
            case Comma:
                return ArithValue(R);
            case Add:
                return ArithValue(L + R);
            case Sub:
                return ArithValue(L - R);
            case Mul:
                return ArithValue(L * R);
            case Div:
                return ArithValue(L / R);
            case Less:
                return ArithValue(L < R);
            case Greater:
                return ArithValue(L > R);
            case Equal:
                return ArithValue(L == R);
            case NotEqual:
                return ArithValue(L != R);
            case LessEqual:
                return ArithValue(L <= R);
            case GreaterEqual:
                return ArithValue(L >= R);
            case LogicalAnd:
                return ArithValue(L && R);
            case LogicalOr:
                return ArithValue(L || R);
            default:
                Utils::Err("not a valid float binary operation");
        }
    }
    Utils::Err("");
    return ArithValue(0);
}

ArithValue ArithOp::Calc(ArithValue rhs, ArithOp op) {
    if (rhs.type == Type::Basic::Int) {
        auto R = rhs.v.i;
        switch (op) {
            case Add:
                return ArithValue(R);
            case Sub:
                return ArithValue(-R);
            case Not:
                return ArithValue(!R);
            case FloatCast:
                return ArithValue((float)R);
            default:
                Utils::Err("not a valid integer unary operation");
        }
    } else if (rhs.type == Type::Basic::Float) {
        auto R = rhs.v.f;
        switch (op) {
            case Add:
                return ArithValue(R);
            case Sub:
                return ArithValue(-R);
            case Not:
                return ArithValue(!R);
            case IntCast:
                return ArithValue((int)R);
            default:
                Utils::Err("not a valid float unary operation");
        }
    }
    Utils::Err("");
    return ArithValue(0);
}

Type::Basic ArithOp::GetType(Type::Basic operand, ArithOp op) {
    switch (op) {
        case Less:
        case Greater:
        case Equal:
        case NotEqual:
        case LessEqual:
        case GreaterEqual:
        case IntCast:
            return Type::Basic::Int;
        case FloatCast:
            return Type::Basic::Float;
        default:
            return operand;
    }
}
